package org.wheel.mybatis.configs;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;

import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.mapping.MappedStatement;
import org.mybatis.spring.annotation.MapperScan;
import org.mybatis.spring.boot.autoconfigure.ConfigurationCustomizer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.wheel.base.utils.ReflectionUtils;
import org.wheel.mybatis.mapper.BaseMapper;

import freemarker.template.Template;

/**
 * @author klaus.wang
 * @description mybatis 自动生成基础crud mapper statement
 */
@Configuration
@MapperScan(basePackages = MyBatisConfig.BASE_PACKAGES)
public class MyBatisConfig implements ConfigurationCustomizer, InitializingBean {

	static final Class<?> baseMapperClass = BaseMapper.class;

	static final Logger logger = LoggerFactory.getLogger(MyBatisConfig.class);

	static Method[] methods = ReflectionUtils.getAllDeclaredMethods(baseMapperClass);

	static final String BASE_PACKAGES = "${mybatis.configuration.variables.basePackages:org.wheel.**.mapper}";

	@Value("#{'" + BASE_PACKAGES + "'.split(',')}")
	private String[] basePackages;

	private Template baseTemplate;

	private freemarker.template.Configuration configuration;

	public MyBatisConfig(freemarker.template.Configuration configuration) {
		this.configuration = configuration;
	}

	@Override
	public void afterPropertiesSet() throws Exception {
		this.baseTemplate = this.configuration.getTemplate("/ibatis/base-mapper.ftl", "UTF-8");
	}

	/**
	 * 实现 ConfigurationCustomizer 自定义方法 添加基础Mapper方法
	 */
	@Override
	public void customize(org.apache.ibatis.session.Configuration configuration) {

		if (basePackages == null || basePackages.length == 0) {

			return;
		}

		Function<Class<?>, Boolean> func = (cls) -> {

			Class<?> interfaces[] = cls.getInterfaces();
			if (interfaces == null || interfaces.length == 0) {
				return true;
			}
			if (Arrays.asList(interfaces).contains(baseMapperClass)) {

				this.addBaseMapper(configuration, cls);
			}

			return true;

		};

		Arrays.asList(basePackages).forEach(pack -> {

			ReflectionUtils.scanPackages(pack, func);
		});
	}

	/**
	 * 自动创建BaseMapper
	 * 
	 * @param config
	 * @param mapCls
	 */
	protected void addBaseMapper(org.apache.ibatis.session.Configuration config, Class<?> mapCls) {

		Class<?> cls = ReflectionUtils.getType(mapCls, 0);
		String mapperName = mapCls.getName(), base = ".base";

		Map<String, Object> dataModel = ReflectionUtils.getClassDataModel(cls);
		dataModel.put("mapperName", mapperName);

		this.processXml(dataModel, (is) -> {
			this.addMapper(config, is, String.format("%s%s", mapperName, base));
		});

		Collection<MappedStatement> mappedStatements = config.getMappedStatements();

		for (Method method : methods) {

			String id = String.format("%s%s.%s", mapperName, base, method.getName());
			if (StringUtils.isBlank(id) || !config.hasStatement(id)) {

				continue;
			}

			MappedStatement ms = config.getMappedStatement(id);

			/**
			 * 构造新MappedStatement
			 */
			MappedStatement.Builder builder = new MappedStatement.Builder(config,
					StringUtils.join(new String[] { mapCls.getName(), method.getName() }, "."), ms.getSqlSource(),
					ms.getSqlCommandType());

			builder.resultMaps(ms.getResultMaps());
			builder.resultSetType(ms.getResultSetType());
			builder.resultOrdered(ms.isResultOrdered());
			builder.statementType(ms.getStatementType());
			builder.keyGenerator(ms.getKeyGenerator());

			/**
			 * 清掉baseMapper statememt
			 */
			mappedStatements.remove(ms);

			/**
			 * 添加
			 */
			config.addMappedStatement(builder.build());
		}

		logger.debug("Added BaseMapper [{}] SUCCEED!", mapCls.getName());
	}

	/**
	 * 生成 基础 mybatis xml
	 */
	void processXml(Map<String, Object> dataModel, Consumer<InputStream> call) {
		if (dataModel == null) {
			return;
		}
		InputStream is = null;
		try (Writer out = new StringWriter()) {
			baseTemplate.process(dataModel, out);
			is = new ByteArrayInputStream(out.toString().getBytes());
			call.accept(is);
			is.close();
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
		} finally {
			// IOUtils.closeQuietly(out, is);
		}
	}

	/**
	 * 动态添加mapper
	 * 
	 * @param configuration
	 * @param is
	 * @param name
	 */
	protected void addMapper(org.apache.ibatis.session.Configuration configuration, InputStream is, String name) {
		try {
			new XMLMapperBuilder(is, configuration, name, configuration.getSqlFragments()).parse();
		} finally {

		}
	}

}
